`timescale 1ns / 1ps
module CIC64 #(
    parameter FOSR = 64,
    parameter FORD = 5
)(
    input wire       clk,
    input wire       rst_n,
    input wire[31:0] data,
    input wire       data_valid,

    output reg       tvalid,
    input wire       tready,
    output reg[31:0] tdata,
    input wire[31:0] data_offset 
);

localparam INT_WIDTH = 32;
localparam DIF_WIDTH = 32;

reg[31:0] INTE[6:0];
reg[31:0] DIF[6:0];
reg[7:0] div_counter;
reg[31:0] offset;
reg[31:0] od;

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        offset <= 0;
    end else begin
        offset <= data_offset;
    end
end

integer i;
always @(negedge clk or negedge rst_n) begin
    if (!rst_n) begin
        for ( i = 0 ;i < FORD ; i = i + 1) begin
            INTE[i] <= 0;
        end
        div_counter <= 1;
    end else if(data_valid) begin
        INTE[0] <= INTE[0] + data;
        for (i = 1; i < FORD; i = i + 1) begin
            INTE[i] <= INTE[i] + INTE[i - 1];
        end
        if (div_counter < FOSR) begin
            div_counter <= div_counter + 1;
        end else begin
            div_counter <= 1;
        end
    end else begin
        for ( i = 0 ;i < FORD; i = i + 1) begin
            INTE[i] <= INTE[i];
        end
    end
end

reg outcount;
integer j;
always @(negedge clk or negedge rst_n) begin
    if (!rst_n) begin
        for ( i =0; i < FORD; i = i + 1) begin
            DIF[i] <= 0;
        end
        outcount <= 0;
        od <= 0;
    end else if(div_counter == FOSR) begin
        for (i = 0; i < FORD; i = i + 1) begin
            case (i)
               0 : DIF[0] <= INTE[FORD - 1];
               1 : DIF[1] <= INTE[FORD - 1] - DIF[0]; 
               2 : DIF[2] <= INTE[FORD - 1] - DIF[0] - DIF[1]; 
               3 : DIF[3] <= INTE[FORD - 1] - DIF[0] - DIF[1] - DIF[2]; 
               4 : DIF[4] <= INTE[FORD - 1] - DIF[0] - DIF[1] - DIF[2] - DIF[3]; 
               5 : DIF[5] <= INTE[FORD - 1] - DIF[0] - DIF[1] - DIF[2] - DIF[3] - DIF[4]; 
               6 : DIF[6] <= INTE[FORD - 1] - DIF[0] - DIF[1] - DIF[2] - DIF[3] - DIF[4] - DIF[5]; 
            endcase
        end
        case (FORD)
           2 : od <= INTE[FORD - 1] - DIF[0] - DIF[1];
           3 : od <= INTE[FORD - 1] - DIF[0] - DIF[1] - DIF[2];
           4 : od <= INTE[FORD - 1] - DIF[0] - DIF[1] - DIF[2] - DIF[3];
           5 : od <= INTE[FORD - 1] - DIF[0] - DIF[1] - DIF[2] - DIF[3] - DIF[4];
           6 : od <= INTE[FORD - 1] - DIF[0] - DIF[1] - DIF[2] - DIF[3] - DIF[4] - DIF[5];
           7 : od <= INTE[FORD - 1] - DIF[0] - DIF[1] - DIF[2] - DIF[3] - DIF[4] - DIF[5] - DIF[6];
        endcase
        outcount <= ~outcount;
    end else begin
        od <= od;
        outcount <= outcount;
        for ( i =0; i < FORD; i = i + 1) begin
            DIF[i] <= DIF[i];
        end
    end
end

reg last_outcount;
reg data_readed;
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        tvalid <= 0;
        last_outcount <= 0;
        tdata <= 0;
    end else if (last_outcount != outcount) begin
        tvalid <= 1;
        tdata <= od + offset;
        last_outcount <= outcount;
    end else if(data_readed == 1) begin
        tvalid <= 0;
        tdata <= tdata;
        last_outcount <= last_outcount;
    end else begin
        tvalid <= tvalid;
        tdata <= tdata;
        last_outcount <= last_outcount;
    end
end

always @(negedge clk or negedge rst_n) begin
    if (!rst_n) begin
        data_readed <= 0;
    end else if (tready == 1 && tvalid == 1) begin
        data_readed <= 1;
    end else begin
        data_readed <= 0;
    end
end
endmodule